%code top{

// Copyright (c) ZeroC, Inc.

}

%code requires{

// Included first to get 'TokenContext' which we need to define YYLTYPE before flex does.
#include "GrammarUtil.h"

// I must set the initial stack depth to the maximum stack depth to
// disable bison stack resizing. The bison stack resizing routines use
// simple malloc/alloc/memcpy calls, which do not work for the
// YYSTYPE, since YYSTYPE is a C++ type, with constructor, destructor,
// assignment operator, etc.
#define YYMAXDEPTH  10000      // 10000 should suffice. Bison default is 10000 as maximum.
#define YYINITDEPTH YYMAXDEPTH // Initial depth is set to max depth, for the reasons described above.

// Newer bison versions allow to disable stack resizing by defining yyoverflow.
#define yyoverflow(a, b, c, d, e, f, g, h) yyerror(a)

}

%code top{

// Defines the rule bison uses to reduce token locations. Bison asks that the macro should
// be one-line, and treatable as a single statement when followed by a semi-colon.
// `N` is the number of tokens that are being combined, and (Cur) is their combined location.
#define YYLLOC_DEFAULT(Cur, Rhs, N)                               \
do                                                                \
    if (N == 1)                                                   \
    {                                                             \
        (Cur) = (YYRHSLOC((Rhs), 1));                             \
    }                                                             \
    else                                                          \
    {                                                             \
        if (N)                                                    \
        {                                                         \
            (Cur).firstLine = (YYRHSLOC((Rhs), 1)).firstLine;     \
            (Cur).firstColumn = (YYRHSLOC((Rhs), 1)).firstColumn; \
        }                                                         \
        else                                                      \
        {                                                         \
            (Cur).firstLine = (YYRHSLOC((Rhs), 0)).lastLine;      \
            (Cur).firstColumn = (YYRHSLOC((Rhs), 0)).lastColumn;  \
        }                                                         \
        (Cur).filename = (YYRHSLOC((Rhs), N)).filename;           \
        (Cur).lastLine = (YYRHSLOC((Rhs), N)).lastLine;           \
        (Cur).lastColumn = (YYRHSLOC((Rhs), N)).lastColumn;       \
    }                                                             \
while(0)

}

%code{

// Forward declaration of the lexing function generated by flex, so bison knows about it.
// This must match the definition of 'yylex' (or 'slice_lex') in the generated scanner.
int slice_lex(YYSTYPE* lvalp, YYLTYPE* llocp);

}

%{

#include "Ice/UUID.h"
#include "Parser.h"

#include <cstring>
#include <limits>

#ifdef _MSC_VER
// warning C4102: 'yyoverflowlab' : unreferenced label
#    pragma warning(disable:4102)
// warning C4127: conditional expression is constant
#    pragma warning(disable:4127)
// warning C4702: unreachable code
#    pragma warning(disable:4702)
#endif

// Avoid old style cast warnings in generated grammar
#ifdef __GNUC__
#    pragma GCC diagnostic ignored "-Wold-style-cast"
#    pragma GCC diagnostic ignored "-Wunused-label"

// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98753
#    pragma GCC diagnostic ignored "-Wfree-nonheap-object"
#endif

// Avoid clang warnings in generated grammar
#if defined(__clang__)
#    pragma clang diagnostic ignored "-Wconversion"
#    pragma clang diagnostic ignored "-Wsign-conversion"
#    pragma clang diagnostic ignored "-Wunused-but-set-variable"
#    pragma clang diagnostic ignored "-Wunused-label"
#endif

using namespace std;
using namespace Slice;

void
slice_error(const char* s)
{
    // yacc and recent versions of Bison use "syntax error" instead
    // of "parse error".

    if (strcmp(s, "parse error") == 0)
    {
        currentUnit->error("syntax error");
    }
    else
    {
        currentUnit->error(s);
    }
}

%}

// Directs Bison to generate a re-entrant parser.
%define api.pure
// Specifies what type to back the tokens with (their semantic values).
%define api.value.type {Slice::GrammarBasePtr}
// Enables Bison's token location tracking functionality.
%locations
// Specify a custom location type for storing the location & filename of tokens.
%define api.location.type {Slice::TokenContext}

// All keyword tokens. Make sure to modify the "keyword" rule in this
// file if the list of keywords is changed. Also make sure to add the
// keyword to the keyword table in Scanner.l.
%token ICE_MODULE
%token ICE_CLASS
%token ICE_INTERFACE
%token ICE_EXCEPTION
%token ICE_STRUCT
%token ICE_SEQUENCE
%token ICE_DICTIONARY
%token ICE_ENUM
%token ICE_OUT
%token ICE_EXTENDS
%token ICE_THROWS
%token ICE_VOID
%token ICE_BOOL
%token ICE_BYTE
%token ICE_SHORT
%token ICE_INT
%token ICE_LONG
%token ICE_FLOAT
%token ICE_DOUBLE
%token ICE_STRING
%token ICE_OBJECT
%token ICE_CONST
%token ICE_FALSE
%token ICE_TRUE
%token ICE_IDEMPOTENT
%token ICE_OPTIONAL
%token ICE_VALUE

// Other tokens.
%token ICE_STRING_LITERAL
%token ICE_INTEGER_LITERAL
%token ICE_FLOATING_POINT_LITERAL
%token ICE_IDENTIFIER
%token ICE_SCOPED_IDENTIFIER
%token ICE_METADATA_OPEN
%token ICE_METADATA_CLOSE
%token ICE_FILE_METADATA_OPEN
%token ICE_FILE_METADATA_CLOSE

// Here 'OPEN' means these tokens end with an open parenthesis.
%token ICE_IDENT_OPEN
%token ICE_KEYWORD_OPEN
%token ICE_OPTIONAL_OPEN

%token BAD_CHAR

%%

// ----------------------------------------------------------------------
start
// ----------------------------------------------------------------------
: definitions
{
}
;

// ----------------------------------------------------------------------
opt_semicolon
// ----------------------------------------------------------------------
: ';'
{
}
| %empty
{
}
;

// ----------------------------------------------------------------------
file_metadata
// ----------------------------------------------------------------------
: ICE_FILE_METADATA_OPEN string_list ICE_FILE_METADATA_CLOSE
{
    $$ = $2;
}
;

// ----------------------------------------------------------------------
metadata
// ----------------------------------------------------------------------
: ICE_METADATA_OPEN string_list ICE_METADATA_CLOSE
{
    $$ = $2;
}
| %empty
{
    $$ = make_shared<StringListTok>();
}
;

// ----------------------------------------------------------------------
definitions
// ----------------------------------------------------------------------
: definitions file_metadata
{
    auto metadata = dynamic_pointer_cast<StringListTok>($2);
    if (!metadata->v.empty())
    {
        currentUnit->addFileMetadata(metadata->v);
    }
}
| definitions metadata definition
{
    auto metadata = dynamic_pointer_cast<StringListTok>($2);
    auto contained = dynamic_pointer_cast<Contained>($3);
    if (contained && !metadata->v.empty())
    {
        contained->setMetadata(metadata->v);
    }
}
| %empty
{
}
;

// ----------------------------------------------------------------------
definition
// ----------------------------------------------------------------------
: module_def
{
    assert($1 == nullptr || dynamic_pointer_cast<Module>($1));
}
opt_semicolon
| class_decl
{
    assert($1 == nullptr || dynamic_pointer_cast<ClassDecl>($1));
}
';'
| class_decl
{
    currentUnit->error("`;' missing after class forward declaration");
}
| class_def
{
    assert($1 == nullptr || dynamic_pointer_cast<ClassDef>($1));
}
opt_semicolon
| interface_decl
{
    assert($1 == nullptr || dynamic_pointer_cast<InterfaceDecl>($1));
}
';'
| interface_decl
{
    currentUnit->error("`;' missing after interface forward declaration");
}
| interface_def
{
    assert($1 == nullptr || dynamic_pointer_cast<InterfaceDef>($1));
}
opt_semicolon
| exception_decl
{
    assert($1 == nullptr);
}
';'
| exception_decl
{
    currentUnit->error("`;' missing after exception forward declaration");
}
| exception_def
{
    assert($1 == nullptr || dynamic_pointer_cast<Exception>($1));
}
opt_semicolon
| struct_decl
{
    assert($1 == nullptr);
}
';'
| struct_decl
{
    currentUnit->error("`;' missing after struct forward declaration");
}
| struct_def
{
    assert($1 == nullptr || dynamic_pointer_cast<Struct>($1));
}
opt_semicolon
| sequence_def
{
    assert($1 == nullptr || dynamic_pointer_cast<Sequence>($1));
}
';'
| sequence_def
{
    currentUnit->error("`;' missing after sequence definition");
}
| dictionary_def
{
    assert($1 == nullptr || dynamic_pointer_cast<Dictionary>($1));
}
';'
| dictionary_def
{
    currentUnit->error("`;' missing after dictionary definition");
}
| enum_def
{
    assert($1 == nullptr || dynamic_pointer_cast<Enum>($1));
}
opt_semicolon
| const_def
{
    assert($1 == nullptr || dynamic_pointer_cast<Const>($1));
}
';'
| const_def
{
    currentUnit->error("`;' missing after const definition");
}
| error ';'
{
    yyerrok;
}
;

// ----------------------------------------------------------------------
module_def
// ----------------------------------------------------------------------
: ICE_MODULE ICE_IDENTIFIER
{
    currentUnit->setSeenDefinition();

    auto ident = dynamic_pointer_cast<StringTok>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    ModulePtr module = cont->createModule(ident->v);
    if (module)
    {
        cont->checkIntroduced(ident->v, module);
        currentUnit->pushContainer(module);
        $$ = module;
    }
    else
    {
        $$ = nullptr;
    }
}
'{' definitions '}'
{
    if ($3)
    {
        currentUnit->popContainer();
        $$ = $3;
    }
    else
    {
        $$ = nullptr;
    }
}
| ICE_MODULE ICE_SCOPED_IDENTIFIER
{
    currentUnit->setSeenDefinition();

    auto ident = dynamic_pointer_cast<StringTok>($2);

    // Reject scoped identifiers starting with "::". This is generally indicates global scope, but is invalid here.
    size_t startPos = 0;
    if (ident->v.find("::") == 0)
    {
        currentUnit->error("illegal identifier: module identifiers cannot start with '::' prefix");
        startPos += 2; // Skip the leading "::".
    }

    // Split the scoped-identifier token into separate module names.
    size_t endPos;
    vector<string> modules;
    while ((endPos = ident->v.find("::", startPos)) != string::npos)
    {
        modules.push_back(ident->v.substr(startPos, (endPos - startPos)));
        startPos = endPos + 2; // Skip the "::" separator.
    }
    modules.push_back(ident->v.substr(startPos));

    // Create the nested modules.
    ContainerPtr cont = currentUnit->currentContainer();
    for (size_t i = 0; i < modules.size(); i++)
    {
        const auto currentModuleName = modules[i];
        ModulePtr module = cont->createModule(currentModuleName);
        if (module)
        {
            cont->checkIntroduced(currentModuleName, module);
            currentUnit->pushContainer(module);
            $$ = cont = module;
        }
        else
        {
            // If an error occurs while creating one of the modules, we have to stop. But, to eagerly report as many
            // errors as possible, we still 'create' any remaining modules, which will run _some_ validation on them.
            for (size_t j = (i + 1); j < modules.size(); j++)
            {
                cont->createModule(modules[j]); // Dummy
            }

            // Then we roll back the chain, ie. pop the successfully-created-modules off the container stack.
            for (; i > 0; i--)
            {
                currentUnit->popContainer();
            }
            $$ = nullptr;
            break;
        }
    }
}
'{' definitions '}'
{
    if ($3)
    {
        // We need to pop '(N+1)' modules off the container stack, to navigate out of the nested module.
        // Where `N` is the number of scope separators ("::").
        size_t startPos = 0;
        auto ident = dynamic_pointer_cast<StringTok>($2);
        while ((startPos = ident->v.find("::", startPos)) != string::npos)
        {
            currentUnit->popContainer();
            startPos += 2; // Skip the "::" separator.
        }

        // Set the 'return value' to the outer-most module, before we pop it off the stack.
        // Whichever module we return, is the one that metadata will be applied to.
        $$ = currentUnit->currentContainer();
        currentUnit->popContainer();
    }
    else
    {
        $$ = nullptr;
    }
}
;

// ----------------------------------------------------------------------
exception_id
// ----------------------------------------------------------------------
: ICE_EXCEPTION ICE_IDENTIFIER
{
    $$ = $2;
}
| ICE_EXCEPTION keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    currentUnit->error("keyword `" + ident->v + "' cannot be used as exception name");
    $$ = $2; // Dummy
}
;

// ----------------------------------------------------------------------
exception_decl
// ----------------------------------------------------------------------
: exception_id
{
    currentUnit->error("exceptions cannot be forward declared");
    $$ = nullptr;
}
;

// ----------------------------------------------------------------------
exception_def
// ----------------------------------------------------------------------
: exception_id exception_extends
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    auto base = dynamic_pointer_cast<Exception>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    ExceptionPtr ex = cont->createException(ident->v, base);
    if (ex)
    {
        cont->checkIntroduced(ident->v, ex);
        currentUnit->pushContainer(ex);
    }
    $$ = ex;
}
'{' data_members '}'
{
    if ($3)
    {
        currentUnit->popContainer();
    }
    $$ = $3;
}
;

// ----------------------------------------------------------------------
exception_extends
// ----------------------------------------------------------------------
: extends scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    ContainedPtr contained = cont->lookupException(scoped->v, true);
    cont->checkIntroduced(scoped->v);
    $$ = contained;
}
| %empty
{
    $$ = nullptr;
}
;

// ----------------------------------------------------------------------
type_id
// ----------------------------------------------------------------------
: type ICE_IDENTIFIER
{
    auto type = dynamic_pointer_cast<Type>($1);
    auto ident = dynamic_pointer_cast<StringTok>($2);
    $$ = make_shared<TypeStringTok>(type, ident->v);
}
;

// ----------------------------------------------------------------------
optional
// ----------------------------------------------------------------------
: ICE_OPTIONAL_OPEN ICE_INTEGER_LITERAL ')'
{
    auto i = dynamic_pointer_cast<IntegerTok>($2);

    int tag;
    if (i->v < 0 || i->v > std::numeric_limits<int32_t>::max())
    {
        currentUnit->error("tag is out of range");
        tag = -1;
    }
    else
    {
        tag = static_cast<int>(i->v);
    }

    auto m = make_shared<OptionalDefTok>(tag);
    $$ = m;
}
| ICE_OPTIONAL_OPEN scoped_name ')'
{
    auto scoped = dynamic_pointer_cast<StringTok>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    assert(cont);
    ContainedList cl = cont->lookupContained(scoped->v, false);
    if (cl.empty())
    {
        EnumeratorList enumerators = cont->enumerators(scoped->v);
        if (enumerators.size() == 1)
        {
            // Found
            cl.push_back(enumerators.front());
            scoped->v = enumerators.front()->scoped();
        }
        else if (enumerators.size() > 1)
        {
            ostringstream os;
            os << "enumerator `" << scoped->v << "' could designate";
            bool first = true;
            for (const auto& p : enumerators)
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    os << " or";
                }

                os << " `" << p->scoped() << "'";
            }
            currentUnit->error(os.str());
        }
        else
        {
            currentUnit->error(string("`") + scoped->v + "' is not defined");
        }
    }

    if (cl.empty())
    {
        YYERROR; // Can't continue, jump to next yyerrok
    }
    cont->checkIntroduced(scoped->v);

    int tag = -1;
    auto enumerator = dynamic_pointer_cast<Enumerator>(cl.front());
    auto constant = dynamic_pointer_cast<Const>(cl.front());
    if (constant)
    {
        auto b = dynamic_pointer_cast<Builtin>(constant->type());
        if (b && b->isIntegralType())
        {
            int64_t l = std::stoll(constant->value(), nullptr, 0);
            if (l < 0 || l > std::numeric_limits<int32_t>::max())
            {
                currentUnit->error("tag is out of range");
            }
            tag = static_cast<int>(l);
        }
    }
    else if (enumerator)
    {
        tag = enumerator->value();
    }

    if (tag < 0)
    {
        currentUnit->error("invalid tag `" + scoped->v + "'");
    }

    auto m = make_shared<OptionalDefTok>(tag);
    $$ = m;
}
| ICE_OPTIONAL_OPEN ')'
{
    currentUnit->error("missing tag");
    auto m = make_shared<OptionalDefTok>(-1); // Dummy
    $$ = m;
}
| ICE_OPTIONAL
{
    currentUnit->error("missing tag");
    auto m = make_shared<OptionalDefTok>(-1); // Dummy
    $$ = m;
}
;

// ----------------------------------------------------------------------
optional_type_id
// ----------------------------------------------------------------------
: optional type_id
{
    auto m = dynamic_pointer_cast<OptionalDefTok>($1);
    auto ts = dynamic_pointer_cast<TypeStringTok>($2);
    m->type = ts->type;
    m->name = ts->name;

    // It's safe to perform this check in the parser, since we already have enough information to know whether a type
    // can be optional. This is because the only types that can be forward declared (classes/interfaces) have constant
    // values for `usesClasses` (true/false respectively).
    if (m->type->usesClasses())
    {
        currentUnit->error("types that use classes cannot be marked with 'optional'");
    }

    $$ = m;
}
| type_id
{
    auto ts = dynamic_pointer_cast<TypeStringTok>($1);
    auto m = make_shared<OptionalDefTok>(-1);
    m->type = ts->type;
    m->name = ts->name;
    $$ = m;
}
;

// ----------------------------------------------------------------------
struct_id
// ----------------------------------------------------------------------
: ICE_STRUCT ICE_IDENTIFIER
{
    $$ = $2;
}
| ICE_STRUCT keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    currentUnit->error("keyword `" + ident->v + "' cannot be used as struct name");
    $$ = $2; // Dummy
}
;

// ----------------------------------------------------------------------
struct_decl
// ----------------------------------------------------------------------
: struct_id
{
    currentUnit->error("structs cannot be forward declared");
    $$ = nullptr; // Dummy
}
;

// ----------------------------------------------------------------------
struct_def
// ----------------------------------------------------------------------
: struct_id
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    StructPtr st = cont->createStruct(ident->v);
    if (st)
    {
        cont->checkIntroduced(ident->v, st);
        currentUnit->pushContainer(st);
    }
    else
    {
        st = cont->createStruct(Ice::generateUUID()); // Dummy
        assert(st);
        currentUnit->pushContainer(st);
    }
    $$ = st;
}
'{' data_members '}'
{
    if ($2)
    {
        currentUnit->popContainer();
    }
    $$ = $2;

    // Empty structures are not allowed
    auto st = dynamic_pointer_cast<Struct>($$);
    assert(st);
    if (st->dataMembers().empty())
    {
        currentUnit->error("struct `" + st->name() + "' must have at least one member"); // $$ is a dummy
    }
}
;

// ----------------------------------------------------------------------
class_name
// ----------------------------------------------------------------------
: ICE_CLASS ICE_IDENTIFIER
{
    $$ = $2;
}
| ICE_CLASS keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    currentUnit->error("keyword `" + ident->v + "' cannot be used as class name");
    $$ = $2; // Dummy
}
;

// ----------------------------------------------------------------------
class_id
// ----------------------------------------------------------------------
: ICE_CLASS ICE_IDENT_OPEN ICE_INTEGER_LITERAL ')'
{
    int64_t id = dynamic_pointer_cast<IntegerTok>($3)->v;
    if (id < 0)
    {
        currentUnit->error("invalid compact id for class: id must be a positive integer");
    }
    else if (id > std::numeric_limits<int32_t>::max())
    {
        currentUnit->error("invalid compact id for class: value is out of range");
    }
    else
    {
        string typeId = currentUnit->getTypeId(static_cast<int>(id));
        if (!typeId.empty())
        {
            currentUnit->error("invalid compact id for class: already assigned to class `" + typeId + "'");
        }
    }

    auto classId = make_shared<ClassIdTok>();
    classId->v = dynamic_pointer_cast<StringTok>($2)->v;
    classId->t = static_cast<int>(id);
    $$ = classId;
}
| ICE_CLASS ICE_IDENT_OPEN scoped_name ')'
{
    auto scoped = dynamic_pointer_cast<StringTok>($3);

    ContainerPtr cont = currentUnit->currentContainer();
    assert(cont);
    ContainedList cl = cont->lookupContained(scoped->v, false);
    if (cl.empty())
    {
        EnumeratorList enumerators = cont->enumerators(scoped->v);
        if (enumerators.size() == 1)
        {
            // Found
            cl.push_back(enumerators.front());
            scoped->v = enumerators.front()->scoped();
        }
        else if (enumerators.size() > 1)
        {
            ostringstream os;
            os << "enumerator `" << scoped->v << "' could designate";
            bool first = true;
            for (const auto& p : enumerators)
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    os << " or";
                }

                os << " `" << p->scoped() << "'";
            }
            currentUnit->error(os.str());
        }
        else
        {
            currentUnit->error(string("`") + scoped->v + "' is not defined");
        }
    }

    if (cl.empty())
    {
        YYERROR; // Can't continue, jump to next yyerrok
    }
    cont->checkIntroduced(scoped->v);

    int id = -1;
    auto enumerator = dynamic_pointer_cast<Enumerator>(cl.front());
    auto constant = dynamic_pointer_cast<Const>(cl.front());
    if (constant)
    {
        auto b = dynamic_pointer_cast<Builtin>(constant->type());
        if (b && b->isIntegralType())
        {
            int64_t l = std::stoll(constant->value(), nullptr, 0);
            if (l < 0 || l > std::numeric_limits<int32_t>::max())
            {
                currentUnit->error("compact id for class is out of range");
            }
            id = static_cast<int>(l);
        }
    }
    else if (enumerator)
    {
        id = enumerator->value();
    }

    if (id < 0)
    {
        currentUnit->error("invalid compact id for class: id must be a positive integer");
    }
    else
    {
        string typeId = currentUnit->getTypeId(id);
        if (!typeId.empty())
        {
            currentUnit->error("invalid compact id for class: already assigned to class `" + typeId + "'");
        }
    }

    auto classId = make_shared<ClassIdTok>();
    classId->v = dynamic_pointer_cast<StringTok>($2)->v;
    classId->t = id;
    $$ = classId;

}
| class_name
{
    auto classId = make_shared<ClassIdTok>();
    classId->v = dynamic_pointer_cast<StringTok>($1)->v;
    classId->t = -1;
    $$ = classId;
}
;

// ----------------------------------------------------------------------
class_decl
// ----------------------------------------------------------------------
: class_name
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    ClassDeclPtr cl = cont->createClassDecl(ident->v);
    $$ = cl;
}
;

// ----------------------------------------------------------------------
class_def
// ----------------------------------------------------------------------
: class_id class_extends
{
    auto ident = dynamic_pointer_cast<ClassIdTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    auto base = dynamic_pointer_cast<ClassDef>($2);
    ClassDefPtr cl = cont->createClassDef(ident->v, ident->t, base);
    if (cl)
    {
        cont->checkIntroduced(ident->v, cl);
        currentUnit->pushContainer(cl);
        $$ = cl;
    }
    else
    {
        $$ = nullptr;
    }
}
'{' data_members '}'
{
    if ($3)
    {
        currentUnit->popContainer();
        $$ = $3;
    }
    else
    {
        $$ = nullptr;
    }
}
;

// ----------------------------------------------------------------------
class_extends
// ----------------------------------------------------------------------
: extends scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    TypeList types = cont->lookupType(scoped->v);
    $$ = nullptr;
    if (!types.empty())
    {
        auto cl = dynamic_pointer_cast<ClassDecl>(types.front());
        if (!cl)
        {
            string msg = "`";
            msg += scoped->v;
            msg += "' is not a class";
            currentUnit->error(msg);
        }
        else
        {
            ClassDefPtr def = cl->definition();
            if (!def)
            {
                string msg = "`";
                msg += scoped->v;
                msg += "' has been declared but not defined";
                currentUnit->error(msg);
            }
            else
            {
                cont->checkIntroduced(scoped->v);
                $$ = def;
            }
        }
    }
}
| %empty
{
    $$ = nullptr;
}
;

// ----------------------------------------------------------------------
extends
// ----------------------------------------------------------------------
: ICE_EXTENDS
{
}
| ':'
{
}
;

// ----------------------------------------------------------------------
data_members
// ----------------------------------------------------------------------
: metadata data_member ';' data_members
{
    auto metadata = dynamic_pointer_cast<StringListTok>($1);
    auto contained = dynamic_pointer_cast<Contained>($2);
    if (contained && !metadata->v.empty())
    {
        contained->setMetadata(metadata->v);
    }
}
| error ';' data_members
{
}
| metadata data_member
{
    currentUnit->error("`;' missing after definition");
}
| %empty
{
}
;

// ----------------------------------------------------------------------
data_member
// ----------------------------------------------------------------------
: optional_type_id
{
    auto def = dynamic_pointer_cast<OptionalDefTok>($1);
    auto cl = dynamic_pointer_cast<ClassDef>(currentUnit->currentContainer());
    DataMemberPtr dm;
    if (cl)
    {
        dm = cl->createDataMember(def->name, def->type, def->isOptional, def->tag, 0, "");
    }
    auto st = dynamic_pointer_cast<Struct>(currentUnit->currentContainer());
    if (st)
    {
        if (def->isOptional)
        {
            currentUnit->error("optional data members are not supported in structs");
            dm = st->createDataMember(def->name, def->type, false, 0, 0, ""); // Dummy
        }
        else
        {
            dm = st->createDataMember(def->name, def->type, false, -1, 0, "");
        }
    }
    auto ex = dynamic_pointer_cast<Exception>(currentUnit->currentContainer());
    if (ex)
    {
        dm = ex->createDataMember(def->name, def->type, def->isOptional, def->tag, 0, "");
    }
    currentUnit->currentContainer()->checkIntroduced(def->name, dm);
    $$ = dm;
}
| optional_type_id '=' const_initializer
{
    auto def = dynamic_pointer_cast<OptionalDefTok>($1);
    auto value = dynamic_pointer_cast<ConstDefTok>($3);
    auto cl = dynamic_pointer_cast<ClassDef>(currentUnit->currentContainer());
    DataMemberPtr dm;
    if (cl)
    {
        dm = cl->createDataMember(def->name, def->type, def->isOptional, def->tag, value->v, value->valueAsString);
    }
    auto st = dynamic_pointer_cast<Struct>(currentUnit->currentContainer());
    if (st)
    {
        if (def->isOptional)
        {
            currentUnit->error("optional data members are not supported in structs");
            dm = st->createDataMember(def->name, def->type, false, 0, 0, ""); // Dummy
        }
        else
        {
            dm = st->createDataMember(def->name, def->type, false, -1, value->v, value->valueAsString);
        }
    }
    auto ex = dynamic_pointer_cast<Exception>(currentUnit->currentContainer());
    if (ex)
    {
        dm = ex->createDataMember(def->name, def->type, def->isOptional, def->tag, value->v, value->valueAsString);
    }
    currentUnit->currentContainer()->checkIntroduced(def->name, dm);
    $$ = dm;
}
| type keyword
{
    auto type = dynamic_pointer_cast<Type>($1);
    string name = dynamic_pointer_cast<StringTok>($2)->v;
    auto cl = dynamic_pointer_cast<ClassDef>(currentUnit->currentContainer());
    if (cl)
    {
        $$ = cl->createDataMember(name, type, false, 0, 0, ""); // Dummy
    }
    auto st = dynamic_pointer_cast<Struct>(currentUnit->currentContainer());
    if (st)
    {
        $$ = st->createDataMember(name, type, false, 0, 0, ""); // Dummy
    }
    auto ex = dynamic_pointer_cast<Exception>(currentUnit->currentContainer());
    if (ex)
    {
        $$ = ex->createDataMember(name, type, false, 0, 0, ""); // Dummy
    }
    assert($$);
    currentUnit->error("keyword `" + name + "' cannot be used as data member name");
}
| type
{
    auto type = dynamic_pointer_cast<Type>($1);
    auto cl = dynamic_pointer_cast<ClassDef>(currentUnit->currentContainer());
    if (cl)
    {
        $$ = cl->createDataMember(Ice::generateUUID(), type, false, 0, 0, ""); // Dummy
    }
    auto st = dynamic_pointer_cast<Struct>(currentUnit->currentContainer());
    if (st)
    {
        $$ = st->createDataMember(Ice::generateUUID(), type, false, 0, 0, ""); // Dummy
    }
    auto ex = dynamic_pointer_cast<Exception>(currentUnit->currentContainer());
    if (ex)
    {
        $$ = ex->createDataMember(Ice::generateUUID(), type, false, 0, 0, ""); // Dummy
    }
    assert($$);
    currentUnit->error("missing data member name");
}
;

// ----------------------------------------------------------------------
return_type
// ----------------------------------------------------------------------
: optional type
{
    auto m = dynamic_pointer_cast<OptionalDefTok>($1);
    m->type = dynamic_pointer_cast<Type>($2);

    // It's safe to perform this check in the parser, since we already have enough information to know whether a type
    // can be optional. This is because the only types that can be forward declared (classes/interfaces) have constant
    // values for `usesClasses` (true/false respectively).
    if (m->type->usesClasses())
    {
        currentUnit->error("types that use classes cannot be marked with 'optional'");
    }

    $$ = m;
}
| type
{
    auto m = make_shared<OptionalDefTok>(-1);
    m->type = dynamic_pointer_cast<Type>($1);
    $$ = m;
}
| ICE_VOID
{
    auto m = make_shared<OptionalDefTok>(-1);
    $$ = m;
}
;

// ----------------------------------------------------------------------
operation_preamble
// ----------------------------------------------------------------------
: return_type ICE_IDENT_OPEN
{
    auto returnType = dynamic_pointer_cast<OptionalDefTok>($1);
    string name = dynamic_pointer_cast<StringTok>($2)->v;
    auto interface = dynamic_pointer_cast<InterfaceDef>(currentUnit->currentContainer());
    if (interface)
    {
        OperationPtr op = interface->createOperation(name, returnType->type, returnType->isOptional, returnType->tag);
        if (op)
        {
            interface->checkIntroduced(name, op);
            currentUnit->pushContainer(op);
            $$ = op;
        }
        else
        {
            $$ = nullptr;
        }
    }
    else
    {
        $$ = nullptr;
    }
}
| ICE_IDEMPOTENT return_type ICE_IDENT_OPEN
{
    auto returnType = dynamic_pointer_cast<OptionalDefTok>($2);
    string name = dynamic_pointer_cast<StringTok>($3)->v;
    auto interface = dynamic_pointer_cast<InterfaceDef>(currentUnit->currentContainer());
    if (interface)
    {
        OperationPtr op = interface->createOperation(
            name,
            returnType->type,
            returnType->isOptional,
            returnType->tag,
            Operation::Idempotent);

        if (op)
        {
            interface->checkIntroduced(name, op);
            currentUnit->pushContainer(op);
            $$ = op;
        }
        else
        {
            $$ = nullptr;
        }
    }
    else
    {
        $$ = nullptr;
    }
}
| return_type ICE_KEYWORD_OPEN
{
    auto returnType = dynamic_pointer_cast<OptionalDefTok>($1);
    string name = dynamic_pointer_cast<StringTok>($2)->v;
    auto interface = dynamic_pointer_cast<InterfaceDef>(currentUnit->currentContainer());
    if (interface)
    {
        OperationPtr op = interface->createOperation(name, returnType->type, returnType->isOptional, returnType->tag);
        if (op)
        {
            currentUnit->pushContainer(op);
            currentUnit->error("keyword `" + name + "' cannot be used as operation name");
            $$ = op; // Dummy
        }
        else
        {
            $$ = nullptr;
        }
    }
    else
    {
        $$ = nullptr;
    }
}
| ICE_IDEMPOTENT return_type ICE_KEYWORD_OPEN
{
    auto returnType = dynamic_pointer_cast<OptionalDefTok>($2);
    string name = dynamic_pointer_cast<StringTok>($3)->v;
    auto interface = dynamic_pointer_cast<InterfaceDef>(currentUnit->currentContainer());
    if (interface)
    {
        OperationPtr op = interface->createOperation(
            name,
            returnType->type,
            returnType->isOptional,
            returnType->tag,
            Operation::Idempotent);
        if (op)
        {
            currentUnit->pushContainer(op);
            currentUnit->error("keyword `" + name + "' cannot be used as operation name");
            $$ = op; // Dummy
        }
        else
        {
            return 0;
        }
    }
    else
    {
        $$ = nullptr;
    }
}
;

// ----------------------------------------------------------------------
operation
// ----------------------------------------------------------------------
: operation_preamble parameters ')'
{
    if ($1)
    {
        currentUnit->popContainer();
        $$ = $1;
    }
    else
    {
        $$ = nullptr;
    }
}
throws
{
    auto op = dynamic_pointer_cast<Operation>($4);
    auto el = dynamic_pointer_cast<ExceptionListTok>($5);
    assert(el);
    if (op)
    {
        op->setExceptionList(el->v);
    }
}
| operation_preamble error ')'
{
    if ($1)
    {
        currentUnit->popContainer();
    }
    yyerrok;
}
throws
{
    auto op = dynamic_pointer_cast<Operation>($4);
    auto el = dynamic_pointer_cast<ExceptionListTok>($5);
    assert(el);
    if (op)
    {
        op->setExceptionList(el->v); // Dummy
    }
}
;

// ----------------------------------------------------------------------
interface_id
// ----------------------------------------------------------------------
: ICE_INTERFACE ICE_IDENTIFIER
{
    $$ = $2;
}
| ICE_INTERFACE keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    currentUnit->error("keyword `" + ident->v + "' cannot be used as interface name");
    $$ = $2; // Dummy
}
;

// ----------------------------------------------------------------------
interface_decl
// ----------------------------------------------------------------------
: interface_id
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    auto cont = currentUnit->currentContainer();
    InterfaceDeclPtr cl = cont->createInterfaceDecl(ident->v);
    cont->checkIntroduced(ident->v, cl);
    $$ = cl;
}
;

// ----------------------------------------------------------------------
interface_def
// ----------------------------------------------------------------------
: interface_id interface_extends
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    auto bases = dynamic_pointer_cast<InterfaceListTok>($2);
    InterfaceDefPtr interface = cont->createInterfaceDef(ident->v, bases->v);
    if (interface)
    {
        cont->checkIntroduced(ident->v, interface);
        currentUnit->pushContainer(interface);
        $$ = interface;
    }
    else
    {
        $$ = nullptr;
    }
}
'{' operations '}'
{
    if ($3)
    {
        currentUnit->popContainer();
        $$ = $3;
    }
    else
    {
        $$ = nullptr;
    }
}
;

// ----------------------------------------------------------------------
interface_list
// ----------------------------------------------------------------------
: scoped_name ',' interface_list
{
    auto intfs = dynamic_pointer_cast<InterfaceListTok>($3);
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    TypeList types = cont->lookupType(scoped->v);
    if (!types.empty())
    {
        auto interface = dynamic_pointer_cast<InterfaceDecl>(types.front());
        if (!interface)
        {
            string msg = "`";
            msg += scoped->v;
            msg += "' is not an interface";
            currentUnit->error(msg);
        }
        else
        {
            InterfaceDefPtr def = interface->definition();
            if (!def)
            {
                string msg = "`";
                msg += scoped->v;
                msg += "' has been declared but not defined";
                currentUnit->error(msg);
            }
            else
            {
                cont->checkIntroduced(scoped->v);
                intfs->v.push_front(def);
            }
        }
    }
    $$ = intfs;
}
| scoped_name
{
    auto intfs = make_shared<InterfaceListTok>();
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    TypeList types = cont->lookupType(scoped->v);
    if (!types.empty())
    {
        auto interface = dynamic_pointer_cast<InterfaceDecl>(types.front());
        if (!interface)
        {
            string msg = "`";
            msg += scoped->v;
            msg += "' is not an interface";
            currentUnit->error(msg); // $$ is a dummy
        }
        else
        {
            InterfaceDefPtr def = interface->definition();
            if (!def)
            {
                string msg = "`";
                msg += scoped->v;
                msg += "' has been declared but not defined";
                currentUnit->error(msg); // $$ is a dummy
            }
            else
            {
                cont->checkIntroduced(scoped->v);
                intfs->v.push_front(def);
            }
        }
    }
    $$ = intfs;
}
| ICE_OBJECT
{
    currentUnit->error("illegal inheritance from type Object");
    $$ = make_shared<InterfaceListTok>(); // Dummy
}
| ICE_VALUE
{
    currentUnit->error("illegal inheritance from type Value");
    $$ = make_shared<InterfaceListTok>(); // Dummy
}
;

// ----------------------------------------------------------------------
interface_extends
// ----------------------------------------------------------------------
: extends interface_list
{
    $$ = $2;
}
| %empty
{
    $$ = make_shared<InterfaceListTok>();
}
;

// ----------------------------------------------------------------------
operations
// ----------------------------------------------------------------------
: metadata operation ';' operations
{
    auto metadata = dynamic_pointer_cast<StringListTok>($1);
    auto contained = dynamic_pointer_cast<Contained>($2);
    if (contained && !metadata->v.empty())
    {
        contained->setMetadata(metadata->v);
    }
}
| error ';' operations
{
}
| metadata operation
{
    currentUnit->error("`;' missing after definition");
}
| %empty
{
}
;

// ----------------------------------------------------------------------
exception_list
// ----------------------------------------------------------------------
: exception ',' exception_list
{
    auto exception = dynamic_pointer_cast<Exception>($1);
    auto exceptionList = dynamic_pointer_cast<ExceptionListTok>($3);
    exceptionList->v.push_front(exception);
    $$ = exceptionList;
}
| exception
{
    auto exception = dynamic_pointer_cast<Exception>($1);
    auto exceptionList = make_shared<ExceptionListTok>();
    exceptionList->v.push_front(exception);
    $$ = exceptionList;
}
;

// ----------------------------------------------------------------------
exception
// ----------------------------------------------------------------------
: scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    ExceptionPtr exception = cont->lookupException(scoped->v, true);
    if (!exception)
    {
        exception = cont->createException(Ice::generateUUID(), 0, Dummy); // Dummy
    }
    cont->checkIntroduced(scoped->v, exception);
    $$ = exception;
}
| keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    currentUnit->error("keyword `" + ident->v + "' cannot be used as exception name");
    $$ = currentUnit->currentContainer()->createException(Ice::generateUUID(), 0, Dummy); // Dummy
}
;

// ----------------------------------------------------------------------
sequence_def
// ----------------------------------------------------------------------
: ICE_SEQUENCE '<' metadata type '>' ICE_IDENTIFIER
{
    auto ident = dynamic_pointer_cast<StringTok>($6);
    auto metadata = dynamic_pointer_cast<StringListTok>($3);
    auto type = dynamic_pointer_cast<Type>($4);
    ContainerPtr cont = currentUnit->currentContainer();
    $$ = cont->createSequence(ident->v, type, metadata->v);
}
| ICE_SEQUENCE '<' metadata type '>' keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($6);
    auto metadata = dynamic_pointer_cast<StringListTok>($3);
    auto type = dynamic_pointer_cast<Type>($4);
    ContainerPtr cont = currentUnit->currentContainer();
    $$ = cont->createSequence(ident->v, type, metadata->v); // Dummy
    currentUnit->error("keyword `" + ident->v + "' cannot be used as sequence name");
}
;

// ----------------------------------------------------------------------
dictionary_def
// ----------------------------------------------------------------------
: ICE_DICTIONARY '<' metadata type ',' metadata type '>' ICE_IDENTIFIER
{
    auto ident = dynamic_pointer_cast<StringTok>($9);
    auto keyMetadata = dynamic_pointer_cast<StringListTok>($3);
    auto keyType = dynamic_pointer_cast<Type>($4);
    auto valueMetadata = dynamic_pointer_cast<StringListTok>($6);
    auto valueType = dynamic_pointer_cast<Type>($7);
    ContainerPtr cont = currentUnit->currentContainer();
    $$ = cont->createDictionary(ident->v, keyType, keyMetadata->v, valueType, valueMetadata->v);
}
| ICE_DICTIONARY '<' metadata type ',' metadata type '>' keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($9);
    auto keyMetadata = dynamic_pointer_cast<StringListTok>($3);
    auto keyType = dynamic_pointer_cast<Type>($4);
    auto valueMetadata = dynamic_pointer_cast<StringListTok>($6);
    auto valueType = dynamic_pointer_cast<Type>($7);
    ContainerPtr cont = currentUnit->currentContainer();
    $$ = cont->createDictionary(ident->v, keyType, keyMetadata->v, valueType, valueMetadata->v); // Dummy
    currentUnit->error("keyword `" + ident->v + "' cannot be used as dictionary name");
}
;

// ----------------------------------------------------------------------
enum_id
// ----------------------------------------------------------------------
: ICE_ENUM ICE_IDENTIFIER
{
    $$ = $2;
}
| ICE_ENUM keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    currentUnit->error("keyword `" + ident->v + "' cannot be used as enumeration name");
    $$ = $2; // Dummy
}
;

// ----------------------------------------------------------------------
enum_def
// ----------------------------------------------------------------------
: enum_id
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    EnumPtr en = cont->createEnum(ident->v);
    if (en)
    {
        cont->checkIntroduced(ident->v, en);
    }
    else
    {
        en = cont->createEnum(Ice::generateUUID(), Dummy);
    }
    currentUnit->pushContainer(en);
    $$ = en;
}
'{' enumerator_list '}'
{
    auto en = dynamic_pointer_cast<Enum>($2);
    if (en)
    {
        auto enumerators = dynamic_pointer_cast<EnumeratorListTok>($4);
        if (enumerators->v.empty())
        {
            currentUnit->error("enum `" + en->name() + "' must have at least one enumerator");
        }
        currentUnit->popContainer();
    }
    $$ = $2;
}
|
ICE_ENUM
{
    currentUnit->error("missing enumeration name");
    ContainerPtr cont = currentUnit->currentContainer();
    EnumPtr en = cont->createEnum(Ice::generateUUID(), Dummy);
    currentUnit->pushContainer(en);
    $$ = en;
}
'{' enumerator_list '}'
{
    currentUnit->popContainer();
    $$ = $1;
}
;

// ----------------------------------------------------------------------
enumerator_list
// ----------------------------------------------------------------------
: metadata enumerator ',' enumerator_list
{
    auto metadata = dynamic_pointer_cast<StringListTok>($1);
    auto enumerator = dynamic_pointer_cast<Enumerator>($2);
    if (enumerator && !metadata->v.empty())
    {
        enumerator->setMetadata(metadata->v);
    }
    auto enumeratorList = dynamic_pointer_cast<EnumeratorListTok>($4);
    enumeratorList->v.push_front(enumerator);
    $$ = enumeratorList;
}
| metadata enumerator
{
    auto metadata = dynamic_pointer_cast<StringListTok>($1);
    auto enumerator = dynamic_pointer_cast<Enumerator>($2);
    if (enumerator && !metadata->v.empty())
    {
        enumerator->setMetadata(metadata->v);
    }
    auto enumeratorList = make_shared<EnumeratorListTok>();
    enumeratorList->v.push_front(enumerator);
    $$ = enumeratorList;
}
| %empty
{
    $$ = make_shared<EnumeratorListTok>(); // Empty list
}
;

// ----------------------------------------------------------------------
enumerator
// ----------------------------------------------------------------------
: ICE_IDENTIFIER
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    EnumPtr cont = dynamic_pointer_cast<Enum>(currentUnit->currentContainer());
    $$ = cont->createEnumerator(ident->v, nullopt);
}
| ICE_IDENTIFIER '=' enumerator_initializer
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    EnumPtr cont = dynamic_pointer_cast<Enum>(currentUnit->currentContainer());
    auto intVal = dynamic_pointer_cast<IntegerTok>($3);
    if (intVal)
    {
        if (intVal->v < 0 || intVal->v > std::numeric_limits<int32_t>::max())
        {
            currentUnit->error("value for enumerator `" + ident->v + "' is out of range");
        }
        $$ = cont->createEnumerator(ident->v, static_cast<int>(intVal->v));
    }
    else
    {
        $$ = cont->createEnumerator(ident->v, nullopt); // Dummy
    }
}
| keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    EnumPtr cont = dynamic_pointer_cast<Enum>(currentUnit->currentContainer());
    currentUnit->error("keyword `" + ident->v + "' cannot be used as enumerator");
    $$ = cont->createEnumerator(ident->v, nullopt); // Dummy
}
;

// ----------------------------------------------------------------------
enumerator_initializer
// ----------------------------------------------------------------------
: ICE_INTEGER_LITERAL
{
    $$ = $1;
}
| scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ContainedList cl = currentUnit->currentContainer()->lookupContained(scoped->v, true);
    IntegerTokPtr tok;
    if (!cl.empty())
    {
        auto constant = dynamic_pointer_cast<Const>(cl.front());
        if (constant)
        {
            currentUnit->currentContainer()->checkIntroduced(scoped->v, constant);
            auto b = dynamic_pointer_cast<Builtin>(constant->type());
            if (b && b->isIntegralType())
            {
                try
                {
                    int64_t v = std::stoll(constant->value(), nullptr, 0);
                    tok = make_shared<IntegerTok>();
                    tok->v = v;
                    tok->literal = constant->value();
                }
                catch (const std::exception&)
                {
                }
            }
        }
    }

    if (!tok)
    {
        string msg = "illegal initializer: `" + scoped->v + "' is not an integer constant";
        currentUnit->error(msg); // $$ is dummy
    }

    $$ = tok;
}
;

// ----------------------------------------------------------------------
out_qualifier
// ----------------------------------------------------------------------
: ICE_OUT
{
    auto out = make_shared<BoolTok>();
    out->v = true;
    $$ = out;
}
| %empty
{
    auto out = make_shared<BoolTok>();
    out->v = false;
    $$ = out;
}
;

// ----------------------------------------------------------------------
parameters
// ----------------------------------------------------------------------
: %empty
{
}
| out_qualifier metadata optional_type_id
{
    auto isOutParam = dynamic_pointer_cast<BoolTok>($1);
    auto tsp = dynamic_pointer_cast<OptionalDefTok>($3);
    auto op = dynamic_pointer_cast<Operation>(currentUnit->currentContainer());
    if (op)
    {
        ParamDeclPtr pd = op->createParamDecl(tsp->name, tsp->type, isOutParam->v, tsp->isOptional, tsp->tag);
        currentUnit->currentContainer()->checkIntroduced(tsp->name, pd);
        auto metadata = dynamic_pointer_cast<StringListTok>($2);
        if (!metadata->v.empty())
        {
            pd->setMetadata(metadata->v);
        }
    }
}
| parameters ',' out_qualifier metadata optional_type_id
{
    auto isOutParam = dynamic_pointer_cast<BoolTok>($3);
    auto tsp = dynamic_pointer_cast<OptionalDefTok>($5);
    auto op = dynamic_pointer_cast<Operation>(currentUnit->currentContainer());
    if (op)
    {
        ParamDeclPtr pd = op->createParamDecl(tsp->name, tsp->type, isOutParam->v, tsp->isOptional, tsp->tag);
        currentUnit->currentContainer()->checkIntroduced(tsp->name, pd);
        auto metadata = dynamic_pointer_cast<StringListTok>($4);
        if (!metadata->v.empty())
        {
            pd->setMetadata(metadata->v);
        }
    }
}
| out_qualifier metadata type keyword
{
    auto isOutParam = dynamic_pointer_cast<BoolTok>($1);
    auto type = dynamic_pointer_cast<Type>($3);
    auto ident = dynamic_pointer_cast<StringTok>($4);
    auto op = dynamic_pointer_cast<Operation>(currentUnit->currentContainer());
    if (op)
    {
        op->createParamDecl(ident->v, type, isOutParam->v, false, 0); // Dummy
        currentUnit->error("keyword `" + ident->v + "' cannot be used as parameter name");
    }
}
| parameters ',' out_qualifier metadata type keyword
{
    auto isOutParam = dynamic_pointer_cast<BoolTok>($3);
    auto type = dynamic_pointer_cast<Type>($5);
    auto ident = dynamic_pointer_cast<StringTok>($6);
    auto op = dynamic_pointer_cast<Operation>(currentUnit->currentContainer());
    if (op)
    {
        op->createParamDecl(ident->v, type, isOutParam->v, false, 0); // Dummy
        currentUnit->error("keyword `" + ident->v + "' cannot be used as parameter name");
    }
}
| out_qualifier metadata type
{
    auto isOutParam = dynamic_pointer_cast<BoolTok>($1);
    auto type = dynamic_pointer_cast<Type>($3);
    auto op = dynamic_pointer_cast<Operation>(currentUnit->currentContainer());
    if (op)
    {
        op->createParamDecl(Ice::generateUUID(), type, isOutParam->v, false, 0); // Dummy
        currentUnit->error("missing parameter name");
    }
}
| parameters ',' out_qualifier metadata type
{
    auto isOutParam = dynamic_pointer_cast<BoolTok>($3);
    auto type = dynamic_pointer_cast<Type>($5);
    auto op = dynamic_pointer_cast<Operation>(currentUnit->currentContainer());
    if (op)
    {
        op->createParamDecl(Ice::generateUUID(), type, isOutParam->v, false, 0); // Dummy
        currentUnit->error("missing parameter name");
    }
}
;

// ----------------------------------------------------------------------
throws
// ----------------------------------------------------------------------
: ICE_THROWS exception_list
{
    $$ = $2;
}
| %empty
{
    $$ = make_shared<ExceptionListTok>();
}
;

// ----------------------------------------------------------------------
scoped_name
// ----------------------------------------------------------------------
: ICE_IDENTIFIER
{
}
| ICE_SCOPED_IDENTIFIER
{
}
;

// ----------------------------------------------------------------------
builtin
// ----------------------------------------------------------------------
: ICE_BOOL {}
| ICE_BYTE {}
| ICE_SHORT {}
| ICE_INT {}
| ICE_LONG {}
| ICE_FLOAT {}
| ICE_DOUBLE {}
| ICE_STRING {}
| ICE_OBJECT {}
| ICE_VALUE {}

// ----------------------------------------------------------------------
type
// ----------------------------------------------------------------------
: ICE_OBJECT '*'
{
    $$ = currentUnit->createBuiltin(Builtin::KindObjectProxy);
}
| builtin
{
    auto typeName = dynamic_pointer_cast<StringTok>($1);
    $$ = currentUnit->createBuiltin(Builtin::kindFromString(typeName->v).value());
}
| scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    if (cont)
    {
        TypeList types = cont->lookupType(scoped->v);
        if (types.empty())
        {
            YYERROR; // Can't continue, jump to next yyerrok
        }
        TypePtr firstType = types.front();

        auto interface = dynamic_pointer_cast<InterfaceDecl>(firstType);
        if (interface)
        {
            string msg = "add a '*' after the interface name to specify its proxy type: '";
            msg += scoped->v;
            msg += "*'";
            currentUnit->error(msg);
            YYERROR; // Can't continue, jump to next yyerrok
        }
        cont->checkIntroduced(scoped->v);

        $$ = firstType;
    }
    else
    {
        $$ = nullptr;
    }
}
| scoped_name '*'
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    if (cont)
    {
        TypeList types = cont->lookupType(scoped->v);
        if (types.empty())
        {
            YYERROR; // Can't continue, jump to next yyerrok
        }
        TypePtr firstType = types.front();

        auto interface = dynamic_pointer_cast<InterfaceDecl>(firstType);
        if (!interface)
        {
            string msg = "`";
            msg += scoped->v;
            msg += "' must be an interface";
            currentUnit->error(msg);
            YYERROR; // Can't continue, jump to next yyerrok
        }
        cont->checkIntroduced(scoped->v);

        $$ = firstType;
    }
    else
    {
        $$ = nullptr;
    }
}
;

// ----------------------------------------------------------------------
string_literal
// ----------------------------------------------------------------------
: ICE_STRING_LITERAL string_literal // Adjacent string literals are concatenated
{
    auto str1 = dynamic_pointer_cast<StringTok>($1);
    auto str2 = dynamic_pointer_cast<StringTok>($2);
    str1->v += str2->v;
}
| ICE_STRING_LITERAL
{
}
;

// ----------------------------------------------------------------------
string_list
// ----------------------------------------------------------------------
: string_list ',' string_literal
{
    auto str = dynamic_pointer_cast<StringTok>($3);
    auto stringList = dynamic_pointer_cast<StringListTok>($1);
    stringList->v.push_back(str->v);
    $$ = stringList;
}
| string_literal
{
    auto str = dynamic_pointer_cast<StringTok>($1);
    auto stringList = make_shared<StringListTok>();
    stringList->v.push_back(str->v);
    $$ = stringList;
}
;

// ----------------------------------------------------------------------
const_initializer
// ----------------------------------------------------------------------
: ICE_INTEGER_LITERAL
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindLong);
    auto intVal = dynamic_pointer_cast<IntegerTok>($1);
    ostringstream sstr;
    sstr << intVal->v;
    auto def = make_shared<ConstDefTok>(type, sstr.str());
    $$ = def;
}
| ICE_FLOATING_POINT_LITERAL
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindDouble);
    auto floatVal = dynamic_pointer_cast<FloatingTok>($1);
    ostringstream sstr;
    sstr << floatVal->v;
    auto def = make_shared<ConstDefTok>(type, sstr.str());
    $$ = def;
}
| scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ConstDefTokPtr def;
    ContainedList cl = currentUnit->currentContainer()->lookupContained(scoped->v, false);
    if (cl.empty())
    {
        // Could be an enumerator
        def = make_shared<ConstDefTok>(nullptr, scoped->v);
    }
    else
    {
        auto enumerator = dynamic_pointer_cast<Enumerator>(cl.front());
        auto constant = dynamic_pointer_cast<Const>(cl.front());
        if (enumerator)
        {
            currentUnit->currentContainer()->checkIntroduced(scoped->v, enumerator);
            def = make_shared<ConstDefTok>(enumerator, scoped->v);
        }
        else if (constant)
        {
            currentUnit->currentContainer()->checkIntroduced(scoped->v, constant);
            def = make_shared<ConstDefTok>(constant, constant->value());
        }
        else
        {
            def = make_shared<ConstDefTok>();
            string msg = "illegal initializer: `" + scoped->v + "' is ";
            string kindOf = cl.front()->kindOf();
            msg += prependA(kindOf);
            currentUnit->error(msg); // $$ is dummy
        }
    }
    $$ = def;
}
| ICE_STRING_LITERAL
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindString);
    auto literal = dynamic_pointer_cast<StringTok>($1);
    auto def = make_shared<ConstDefTok>(type, literal->v);
    $$ = def;
}
| ICE_FALSE
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindBool);
    auto literal = dynamic_pointer_cast<StringTok>($1);
    auto def = make_shared<ConstDefTok>(type, "false");
    $$ = def;
}
| ICE_TRUE
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindBool);
    auto literal = dynamic_pointer_cast<StringTok>($1);
    auto def = make_shared<ConstDefTok>(type, "true");
    $$ = def;
}
;

// ----------------------------------------------------------------------
const_def
// ----------------------------------------------------------------------
: ICE_CONST metadata type ICE_IDENTIFIER '=' const_initializer
{
    auto metadata = dynamic_pointer_cast<StringListTok>($2);
    auto const_type = dynamic_pointer_cast<Type>($3);
    auto ident = dynamic_pointer_cast<StringTok>($4);
    auto value = dynamic_pointer_cast<ConstDefTok>($6);
    $$ = currentUnit->currentContainer()->createConst(ident->v, const_type, metadata->v, value->v,
                                                      value->valueAsString);
}
| ICE_CONST metadata type '=' const_initializer
{
    auto metadata = dynamic_pointer_cast<StringListTok>($2);
    auto const_type = dynamic_pointer_cast<Type>($3);
    auto value = dynamic_pointer_cast<ConstDefTok>($5);
    currentUnit->error("missing constant name");
    $$ = currentUnit->currentContainer()->createConst(Ice::generateUUID(), const_type, metadata->v, value->v,
                                                      value->valueAsString, Dummy); // Dummy
}
;

// ----------------------------------------------------------------------
keyword
// ----------------------------------------------------------------------
: ICE_MODULE {}
| ICE_CLASS {}
| ICE_INTERFACE {}
| ICE_EXCEPTION {}
| ICE_STRUCT {}
| ICE_SEQUENCE {}
| ICE_DICTIONARY {}
| ICE_ENUM {}
| ICE_OUT {}
| ICE_EXTENDS {}
| ICE_THROWS {}
| ICE_VOID {}
| ICE_BOOL {}
| ICE_BYTE {}
| ICE_SHORT {}
| ICE_INT {}
| ICE_LONG {}
| ICE_FLOAT {}
| ICE_DOUBLE {}
| ICE_STRING {}
| ICE_OBJECT {}
| ICE_CONST {}
| ICE_FALSE {}
| ICE_TRUE {}
| ICE_IDEMPOTENT {}
| ICE_OPTIONAL {}
| ICE_VALUE {}
;

%%
